Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Support publishing and consuming chain storage stream cells #5942

Merged
merged 20 commits into from
Aug 20, 2022

Conversation

gibson042
Copy link
Member

@gibson042 gibson042 commented Aug 12, 2022

Fixes #5366
Fixes #5508

Description

Adds support for publishing and opportunistically consuming { height, values } chain storage stream cells in place of naked values, and switches the "published" chain storage subtree to use them.

I'm not sure how casting is intended to behave with batches, and confused about the committer.isValid() guard.

Security Considerations

There's a minor risk of stored data getting confused for a stream cell, but since everything is currently under our control, that should be fine until we can eliminate naked values (at least in the "published" subtree).

Documentation Considerations

I'm not sure how to document the new structure, which I'm calling a stream cell per https://github.com/Agoric/agoric-sdk/tree/mfig-vstream/golang/cosmos/x/vstream/spec .

Testing Considerations

It looks like there are no current tests for the Go aspects, and how to add some isn't clear.

@gibson042 gibson042 added enhancement New feature or request javascript Pull requests that update Javascript code labels Aug 12, 2022
@gibson042 gibson042 marked this pull request as draft August 12, 2022 10:07
@gibson042 gibson042 changed the title Gibson 5508 gap free chain storage feat: Support publishing and opportunistically consuming { height, values } chain storage stream cells Aug 12, 2022
@gibson042 gibson042 changed the title feat: Support publishing and opportunistically consuming { height, values } chain storage stream cells feat: Support publishing and opportunistically consuming chain storage stream cells Aug 12, 2022
@gibson042 gibson042 changed the title feat: Support publishing and opportunistically consuming chain storage stream cells feat: Support publishing and consuming chain storage stream cells Aug 12, 2022
@gibson042 gibson042 changed the title feat: Support publishing and consuming chain storage stream cells feat!: Support publishing and consuming chain storage stream cells Aug 12, 2022
@gibson042 gibson042 marked this pull request as ready for review August 12, 2022 18:56
.height as $dataHeight |

# Flatten each value independently.
.values[] | fromjson |
Copy link
Member

@dckc dckc Aug 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered if this change had any impact on this script. Do we have any tests that would have failed if we didn't remember to update this script?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure we don't have any tests of shell scripts, and I think that's fine. They're not exported from the packages and not part of the API of the package. They're just here to help organize the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's perhaps not exported in the npm sense, but it's a customer-visible API (customer-required, in fact). I would like to see an integration test where

  • javascript code writes capData to a file
  • this script reads the capData file and writes flat data
  • we diff the flat data against expected results

Of course, I'd like to see a lot of things... I'm not sure whether it's cost-effective any time soon. It's a question of how often we're likely to tweak this script.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it should be exported in the npm sense? that is: perhaps it should go in .bin?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with a test like you describe above is that it's mostly navel-gazing—there's no connection between the hard-coded input and the kind of output that is expected of RPC nodes in the real world. But I do still thank that having something is a good idea.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I've added shell-based testing. Any ideas for how to make sure they run with the rest of our tests?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test nom script could call the shell test thing, maybe? (I haven't looked at the shell thing)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a simple runner at scripts/test/test.sh (and a GitHub workflow calling it).

Copy link
Member

@turadg turadg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the chain JS. I don't know enough about the Go parts to review so not approving the PR.

If you'd like me to dive in enough to do a full review let's discuss because I'll have to reprioritize some stuff.

packages/casting/src/follower-cosmjs.js Outdated Show resolved Hide resolved
packages/casting/src/follower-cosmjs.js Outdated Show resolved Hide resolved
@@ -64,10 +64,19 @@ const fakeStatusResult = {
},
};

export const startFakeServer = (t, fakeValues, marshaller = makeMarshal()) => {
/** @typedef {Partial<import('ava').ExecutionContext<{cleanups: Array<() => void>}>> & {context}} fakeServerTestContext */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

packages/casting/test/test-mvp.js Show resolved Hide resolved
@@ -38,11 +38,14 @@ harden(sanitizePathSegment);
* @param {(message: StorageMessage) => any} handleStorageMessage a function for sending a storageMessage object to the storage implementation (cf. golang/cosmos/x/vstorage/vstorage.go)
* @param {string} storeName currently limited to "swingset"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drive-by: type is effectively {'swingset'}

// Instantiate chain storage over a simple in-memory implementation.
// makeFakeChainStorageKit creates a chainStorage root node over an in-memory map
// and exposes both the map and the sequence of received messages.
const makeFakeChainStorageKit = (rootPath, options) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have more than one kind of fake storage. How about makeTransientChainStorageKit? Not sure we even need "chain" #5945

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, WDYT of this in lib-chainStorage for use by other packages?

Comment on lines 6 to 7
// makeFakeChainStorageKit creates a chainStorage root node over an in-memory map
// and exposes both the map and the sequence of received messages.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better as jsdoc. can include param types as well

.height as $dataHeight |

# Flatten each value independently.
.values[] | fromjson |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure we don't have any tests of shell scripts, and I think that's fine. They're not exported from the packages and not part of the API of the package. They're just here to help organize the code.

@turadg turadg mentioned this pull request Aug 12, 2022
// but would add a requirement exclusive to AppendStorageValueAndNotify that its input be JSON.
// On the other hand, we could always extend this format in the future to include an indication
// that values are subject to a different encoding, e.g. `"valueEncoding":"base64"`.
Values []string `json:"values"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think string also limits cells to JSON (or at least text formats) in a way that []byte would not. I think we should commit to JSON. Strings might be better since interface{} has a lot more liberty to mess up 64 bit integers among other things.

Copy link
Member Author

@gibson042 gibson042 Aug 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to stick with stream cell values being strings, aligning with the existing interfaces. So from the outside in we have

  • JS ChainStorageNode option "sequence" is false (default) vs. true.
  • JS ChainStorageNode method setValue(value: string) sends value with method "set" vs. "append".
  • vstorage.go invokes keeper.SetStorageAndNotify vs. keeper.AppendStorageValueAndNotify, in either case with the received value.
  • AppendStorageValueAndNotify loads or initializes a stream cell struct { Height string, Values []string }, appends the value, and calls SetStorageAndNotify with the cell's JSON serialization (this representation of a stream cell as JSON is the opinionated decision).
  • SetStorageAndNotify works down to SetStorage, which performs the actual write of value as a []byte with a static prefix.

// otherwise initialize a blank cell.
currentData := k.GetData(ctx, path)
var cell StreamCell
_ = json.Unmarshal([]byte(currentData), &cell)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentionally suppressed here because we populate cell manually if Unmarshal fails to do so.

return;
let streamCell = decode(buf);
// Upgrade a naked value to a JSON stream cell if necessary.
if (!streamCell.height || !streamCell.values) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 0 a valid height?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because it's not a string. And "0" is truthy.

// eslint-disable-next-line no-continue
continue;
}
committer.commit({ value });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What’s the envelope around value for? I only see { value } getting committed, not { value, done }, the one likely suspect.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't actually figured that out yet.

Comment on lines 363 to 361
if (!last) {
committer = prepareUpdateInOrder();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why replace the committer only if final?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if not final. And I think replacing it in final position would make it look like there are gaps, although I could be wrong.

export const startFakeServer = (t, fakeValues, marshaller = makeMarshal()) => {
/** @typedef {Partial<import('ava').ExecutionContext<{cleanups: Array<() => void>}>> & {context}} fakeServerTestContext */
/**
* @param {fakeServerTestContext} t
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for the type to have a little f?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I'll capitalize it.

/** @typedef {Partial<import('ava').ExecutionContext<{cleanups: Array<() => void>}>> & {context}} fakeServerTestContext */
/**
* @param {fakeServerTestContext} t
* @param {Array<{any}>} fakeValues
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does {any} mean?

Copy link
Member Author

@gibson042 gibson042 Aug 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, it imposes no constraints beyond existence.

Comment on lines 77 to 78
lastPort += 1;
const PORT = lastPort;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside: this usually tells me we should listen on 0 and ask the kernel for the port we were assigned.

@arirubinstein
Copy link
Contributor

@gibson042
I ran into an issue with the jq parse on vault managers:

./test.sh https://xnet.rpc.agoric.net published.vaultFactory.manager0.metrics                                                       5 ↵
jq: error (at <stdin>:16): null (null) cannot be matched, as it is not a string

@gibson042
Copy link
Member Author

@arirubinstein I think that was from iface missing in repeated references to the same slot, which should now be fixed. PTAL.

@gibson042
Copy link
Member Author

@arirubinstein I'd also like to rename blockHeight to responseBlockHeight, which would be a breaking change in the script output but provide more clarity in contrast with dataBlockHeight. Any objections?

@gibson042 gibson042 force-pushed the gibson-5508-gap-free-chain-storage branch 2 times, most recently from e5f29c9 to 89daa67 Compare August 18, 2022 18:49
@gibson042 gibson042 added the automerge:rebase Automatically rebase updates, then merge label Aug 20, 2022
@turadg turadg force-pushed the gibson-5508-gap-free-chain-storage branch from 672526e to 2ce41da Compare August 20, 2022 22:04
@mergify mergify bot merged commit d8ede91 into master Aug 20, 2022
@mergify mergify bot deleted the gibson-5508-gap-free-chain-storage branch August 20, 2022 23:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:rebase Automatically rebase updates, then merge enhancement New feature or request javascript Pull requests that update Javascript code
Projects
None yet
5 participants